﻿//"The contents of this file are subject to the Mozilla Public License
//Version 1.1 (the "License"); you may not use this file except in
//compliance with the License. You may obtain a copy of the License at
//http://www.mozilla.org/MPL/

//Software distributed under the License is distributed on an "AS IS"
//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//License for the specific language governing rights and limitations
//under the License.

//The Original Code is TSharp.

//The Initial Developer of the Original Code is Mar3ek (http://mar3ek.wordpress.com).
//Portions created by Mar3ek are Copyright (C) 2010
//Mar3ek (http://mar3ek.wordpress.com). All Rights Reserved.

//Contributor(s): ______________________________________.

//Alternatively, the contents of this file may be used under the terms
//of the _____ license (the  "[___] License"), in which case the
//provisions of [______] License are applicable instead of those
//above.  If you wish to allow use of your version of this file only
//under the terms of the [____] License and not to allow others to use
//your version of this file under the MPL, indicate your decision by
//deleting  the provisions above and replace  them with the notice and
//other provisions required by the [___] License.  If you do not delete
//the provisions above, a recipient may use your version of this file
//under either the MPL or the [___] License."

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace TSharp
{
    public partial class Twitter
    {
        #region User

        /// <summary>
        /// Returns extended information about the given user, specified by ID
        /// </summary>
        /// <param name="id">The ID of the user for whom to return results for</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Twitter user</returns>
        public TwitterUser ShowUser(ulong id)
        {
            if (id < 1) throw new ArgumentOutOfRangeException("id");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/users/show.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", id.ToString()));

            var doc = XDocument.Parse(result);
            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Returns extended information about the given user, specified by screen name
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Twitter user</returns>
        public TwitterUser ShowUser(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentOutOfRangeException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/users/show.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenName));

            var doc = XDocument.Parse(result);
            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Returns extended information about the given user
        /// </summary>
        /// <param name="user">The screen name of the user for whom to return results for</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Twitter user</returns>
        public TwitterUser ShowUser(TwitterUser user)
        {
            if (user == null) throw new ArgumentOutOfRangeException("user");

            return ShowUser(user.ScreenName);
        }

        /// <summary>
        /// Return up to 100 users worth of extended information, specified by IDs
        /// </summary>
        /// <param name="ids">Array of user IDs, up to 100 are allowed in a single request</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> LookupUsers(params ulong[] ids)
        {
            if (ids == null || ids.Length == 0) throw new ArgumentNullException("ids");
            if (ids.Length > 100) throw new ArgumentOutOfRangeException("ids", "Too many parameters");

            var userIdsJoined = string.Join(",", (from id in ids select id.ToString()).ToArray());

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/users/lookup.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userIdsJoined));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Return up to 100 users worth of extended information, specified by screen names
        /// </summary>
        /// <param name="screenNames">Array of screen names, up to 100 are allowed in a single request</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> LookupUsers(params string[] screenNames)
        {
            if (screenNames == null || screenNames.Length == 0) throw new ArgumentNullException("screenNames");
            if (screenNames.Length > 100) throw new ArgumentOutOfRangeException("screenNames", "Too many parameters");

            var screenNamesJoined = string.Join(",", screenNames);

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/users/lookup.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenNamesJoined));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Return up to 100 users worth of extended information, specified by screen names
        /// </summary>
        /// <param name="users">Array of users, up to 100 are allowed in a single request</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> LookupUsers(params TwitterUser[] users)
        {
            if (users == null || users.Length == 0) throw new ArgumentNullException("users");
            if (users.Length > 100) throw new ArgumentOutOfRangeException("users", "Too many parameters");

            var screenNamesJoined = string.Join(",", users.Select(u => u.ScreenName).ToArray());

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/users/lookup.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenNamesJoined));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Return up to 100 users worth of extended information, specified by ids or screen names
        /// </summary>
        /// <param name="ids">Array of user IDs, up to 100 are allowed in a single request</param>
        /// <param name="screenNames">Array of screen names, up to 100 are allowed in a single request</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> LookupUsers(ulong[] ids, string[] screenNames)
        {
            if (ids == null || ids.Length == 0) throw new ArgumentNullException("ids");
            if (ids.Length > 100) throw new ArgumentOutOfRangeException("ids", "Too many parameters");

            if (screenNames == null || screenNames.Length == 0) throw new ArgumentNullException("screenNames");
            if (screenNames.Length > 100) throw new ArgumentOutOfRangeException("screenNames", "Too many parameters");

            var userIdsJoined = string.Join(",", (from id in ids select id.ToString()).ToArray());
            var userScreenNamesJoined = string.Join(",", screenNames);

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/users/lookup.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userIdsJoined),
                                        new KeyValuePair<string, string>("screen_name", userScreenNamesJoined));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Runs a search for users similar to Find People button on Twitter.com.
        /// </summary>
        /// <param name="query">The search query to run against people search (will be URL encoded automatically)</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Only the first 1000 matches are available.</remarks>
        public Collection<TwitterUser> SearchUsers(string query)
        {
            if (string.IsNullOrEmpty(query)) throw new ArgumentNullException("query");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/users/search.xml",
                                        true,
                                        new KeyValuePair<string, string>("q", Globals.ProperUrlEscape(query)));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Access to Twitter's suggested user list.
        /// </summary>
        /// <param name="language">Specifies the language in which to return suggestions</param>
        /// <returns>List of suggested categories</returns>
        /// <remarks>The category can be used in the SuggestedUsersFromCategory() endpoint to get the users in that category.</remarks>
        public Collection<SuggestedCategory> GetSuggestedCategories(TwitterSuggestionLangugage language)
        {
            string lang;
            switch (language)
            {
                case TwitterSuggestionLangugage.English:
                    lang = "en";
                    break;
                case TwitterSuggestionLangugage.French:
                    lang = "fr";
                    break;
                case TwitterSuggestionLangugage.German:
                    lang = "de";
                    break;
                case TwitterSuggestionLangugage.Italian:
                    lang = "it";
                    break;
                default:
                    lang = "es";
                    break;
            }

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/users/suggestions.xml",
                                        true, new KeyValuePair<string, string>("lang", lang));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<SuggestedCategory>(doc.Element("suggestions").Elements("category").Select(categoryElement => new SuggestedCategory(categoryElement.Element("name").Value, categoryElement.Element("slug").Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Access to Twitter's suggested user list.
        /// </summary>
        /// <returns>List of suggested categories</returns>
        /// <remarks>The category can be used in the SuggestedUsersFromCategory() endpoint to get the users in that category.</remarks>
        public Collection<SuggestedCategory> GetSuggestedCategories()
        {
            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/users/suggestions.xml",
                                        true);

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<SuggestedCategory>(doc.Element("suggestions").Elements("category").Select(categoryElement => new SuggestedCategory(categoryElement.Element("name").Value, categoryElement.Element("slug").Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Access the users in a given category of the Twitter suggested user list.
        /// </summary>
        /// <param name="slug">The short name of list or a category</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>It is recommended that end clients cache this data for no more than one hour.</remarks>
        public Collection<TwitterUser> GetSuggestedUsersFromCategory(string slug)
        {
            if (string.IsNullOrEmpty(slug)) throw new ArgumentNullException("slug");

            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/users/suggestions/{0}.xml", slug),
                                        true);

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("category").Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Access the users in a given category of the Twitter suggested user list.
        /// </summary>
        /// <param name="category">The target a category</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>It is recommended that end clients cache this data for no more than one hour.</remarks>
        public Collection<TwitterUser> GetSuggestedUsersFromCategory(SuggestedCategory category)
        {
            if (category == null) throw new ArgumentNullException("category");

            return GetSuggestedUsersFromCategory(category.Slug);
        }

        /// <summary>
        /// Access the users in a given category of the Twitter suggested user list.
        /// </summary>
        /// <param name="slug">The short name of list or a category</param>
        /// <param name="language">Language in which to return suggestions</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>It is recommended that end clients cache this data for no more than one hour.</remarks>
        public Collection<TwitterUser> GetSuggestedUsersFromCategory(string slug, TwitterSuggestionLangugage language)
        {
            if (string.IsNullOrEmpty(slug)) throw new ArgumentNullException("slug");

            string lang;
            switch (language)
            {
                case TwitterSuggestionLangugage.English:
                    lang = "en";
                    break;
                case TwitterSuggestionLangugage.French:
                    lang = "fr";
                    break;
                case TwitterSuggestionLangugage.German:
                    lang = "de";
                    break;
                case TwitterSuggestionLangugage.Italian:
                    lang = "it";
                    break;
                default:
                    lang = "es";
                    break;
            }

            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/users/suggestions/{0}.xml", slug),
                                        true, new KeyValuePair<string, string>("lang", lang));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("category").Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Access the users in a given category of the Twitter suggested user list.
        /// </summary>
        /// <param name="category">The short name of list or a category</param>
        /// <param name="language">Language in which to return suggestions</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>It is recommended that end clients cache this data for no more than one hour.</remarks>
        public Collection<TwitterUser> GetSuggestedUsersFromCategory(SuggestedCategory category, TwitterSuggestionLangugage language)
        {
            if (category == null) throw new ArgumentNullException("category");

            return GetSuggestedUsersFromCategory(category.Slug, language);
        }
        
        /// <summary>
        /// Gets a list of authors in the specified suggestion category
        /// </summary>
        /// <param name="slug">Category slug</param>
        /// <returns>List of users</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        public Collection<TwitterUser> GetSuggestedCategoryMembers(string slug)
        {
            if (string.IsNullOrEmpty(slug)) throw new ArgumentNullException("slug");

            var response = SendRequest(RequestMethod.Get,
                                       string.Format("https://api.twitter.com/1/users/suggestions/{0}/members.xml", slug),
                                       true);
            var doc = XDocument.Parse(response);

            var root = doc.Element("suggestions");
            var result = new Collection<TwitterUser>();
// ReSharper disable PossibleNullReferenceException
            foreach (var userElement in root.Elements("user"))
            {
                result.Add(ParseAuthor(userElement));
            }
// ReSharper restore PossibleNullReferenceException
            return result;
        }

        /// <summary>
        /// Gets a list of authors in the specified suggestion category
        /// </summary>
        /// <param name="category">Target category</param>
        /// <returns>List of users</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        public Collection<TwitterUser> GetSuggestedCategoryMembers(SuggestedCategory category)
        {
            if (category == null) throw new ArgumentNullException("category");

            return GetSuggestedCategoryMembers(category.Slug);
        }

        /// <summary>
        /// Access the profile image in various sizes for the user with the indicated screen name.
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <param name="imageSize">Specifies the size of image to fetch</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>URL of the image</returns>
        /// <remarks>This method should only be used by application developers to lookup or check the profile image URL for a user. This method <b>must not be used</b> as the image source URL presented to users of your application.</remarks>
        public Uri GetProfileImageUrl(string screenName, ProfileImageSize imageSize)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            string imageSizeParam;

            switch (imageSize)
            {
                case ProfileImageSize.Bigger:
                    imageSizeParam = "bigger";
                    break;
                case ProfileImageSize.Mini:
                    imageSizeParam = "mini";
                    break;
                default:
                    imageSizeParam = "normal";
                    break;
            }

            var result = SendRequest(RequestMethod.Get,
                                        string.Format("https://api.twitter.com/1/users/profile_image/{0}.xml",
                                                      screenName),
                                        false,
                                        new KeyValuePair<string, string>("size", imageSizeParam));

            Uri resultUri = null;
            const string linkRegexString = @"http://([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&amp;\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?";
            var link = Regex.Match(result, linkRegexString, RegexOptions.Compiled).Value;

            if (string.IsNullOrEmpty(link))
                resultUri = new Uri(link);

            return resultUri;
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFriends(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/friends.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenName));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFriends(string screenName, long cursor, out ulong previousCursor,
                                                 out ulong nextCursor)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/friends.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenName),
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var usersList = doc.Element("users_list");

            // ReSharper disable PossibleNullReferenceException
            var users = new Collection<TwitterUser>(usersList.Element("users").Elements("user").Select(ParseAuthor).ToList());

            nextCursor = ulong.Parse(usersList.Element("next_cursor").Value);
            previousCursor = ulong.Parse(usersList.Element("previous_cursor").Value);
            // ReSharper restore PossibleNullReferenceException
            return users;
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="user">The user for whom to return results for</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFriends(TwitterUser user)
        {
            if (user == null) throw new ArgumentNullException("user");

            return GetUsersFriends(user.ScreenName);
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="user">The user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFriends(TwitterUser user, long cursor, out ulong previousCursor,
                                                 out ulong nextCursor)
        {
            if (user == null) throw new ArgumentNullException("user");

            return GetUsersFriends(user.ScreenName, cursor, out previousCursor, out nextCursor);
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="userId">The ID of the user for whom to return results for</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFriends(ulong userId)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/friends.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="userId">The ID of the user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFriends(ulong userId, long cursor, out ulong previousCursor,
                                                 out ulong nextCursor)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");
            //if (cursor == 0) throw new ArgumentNullException("cursor", "Cursor must not be zero. If you don't want to paginate the results, use GetFriendsUsers instead.");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/friends.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()),
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var usersList = doc.Element("users_list");

            // ReSharper disable PossibleNullReferenceException
            var users = new Collection<TwitterUser>(usersList.Element("users").Elements("user").Select(ParseAuthor).ToList());

            nextCursor = ulong.Parse(usersList.Element("next_cursor").Value);
            previousCursor = ulong.Parse(usersList.Element("previous_cursor").Value);
            // ReSharper restore PossibleNullReferenceException
            return users;
        }

        /// <summary>
        /// Returns the user's followers.
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which they followed the user. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFollowers(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/followers.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenName));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns the user's followers.
        /// </summary>
        /// <param name="screenName">The screen name of the user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which they followed the user. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFollowers(string screenName, long cursor, out ulong previousCursor,
                                                   out ulong nextCursor)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/followers.xml",
                                        true,
                                        new KeyValuePair<string, string>("screen_name", screenName),
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var usersList = doc.Element("users_list");

            // ReSharper disable PossibleNullReferenceException
            var users = new Collection<TwitterUser>(usersList.Element("users").Elements("user").Select(ParseAuthor).ToList());

            nextCursor = ulong.Parse(usersList.Element("next_cursor").Value);
            previousCursor = ulong.Parse(usersList.Element("previous_cursor").Value);
            // ReSharper restore PossibleNullReferenceException
            return users;
        }

        /// <summary>
        /// Returns the user's followers.
        /// </summary>
        /// <param name="userId">The ID of the user for whom to return results for</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which they followed the user. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFollowers(ulong userId)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/followers.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()));

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<TwitterUser>(doc.Element("users").Elements("user").Select(ParseAuthor).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns the user's followers.
        /// </summary>
        /// <param name="userId">The ID of the user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which they followed the user. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFollowers(ulong userId, long cursor, out ulong previousCursor,
                                                   out ulong nextCursor)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/statuses/followers.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()),
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var usersList = doc.Element("users_list");

            // ReSharper disable PossibleNullReferenceException
            var users = new Collection<TwitterUser>(usersList.Element("users").Elements("user").Select(ParseAuthor).ToList());

            nextCursor = ulong.Parse(usersList.Element("next_cursor").Value);
            previousCursor = ulong.Parse(usersList.Element("previous_cursor").Value);
            // ReSharper restore PossibleNullReferenceException
            return users;
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="user">The user for whom to return results for</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        public Collection<TwitterUser> GetUsersFollowers(TwitterUser user)
        {
            if (user == null) throw new ArgumentNullException("user");

            return GetUsersFollowers(user.ScreenName);
        }

        /// <summary>
        /// Returns a user's friends.
        /// </summary>
        /// <param name="user">The user for whom to return results for</param>
        /// <param name="cursor">Breaks the results into pages. This is recommended for users who are following many users. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the next page</param>
        /// <param name="nextCursor">Start index of the previous page</param>
        /// <remarks>Users are ordered by the order in which the user followed them, most recently followed first, 100 at a time. Suspended users will be filtered out.</remarks>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>List of users</returns>
        public Collection<TwitterUser> GetUsersFollowers(TwitterUser user, long cursor, out ulong previousCursor,
                                                 out ulong nextCursor)
        {
            if (user == null) throw new ArgumentNullException("user");

            return GetUsersFollowers(user.ScreenName, cursor, out previousCursor, out nextCursor);
        }

        #endregion

        /// <summary>
        /// Parses TwitterUser from XML data
        /// </summary>
        /// <param name="user">XElement containing user data</param>
        /// <returns>TwitterUser</returns>
        /// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception>
        private static TwitterUser ParseAuthor(XElement user)
        {
            if (user == null) throw new ArgumentNullException("user");

            var showInlineMedia = false;
            var retweetCountElement = user.Element("show_all_inline_media");
            if (retweetCountElement != null)
            {
                bool.TryParse(retweetCountElement.Value, out showInlineMedia);
            }

            ulong listedCount = 0;
            var listedCountElement = user.Element("listed_count");
            if (listedCountElement != null)
            {
                ulong.TryParse(listedCountElement.Value, out listedCount);
            }

            var followRequestSent = false;
            var followRequestSentElement = user.Element("follow_request_sent");
            if (followRequestSentElement != null)
            {
                bool.TryParse(followRequestSentElement.Value, out followRequestSent);
            }

            // ReSharper disable PossibleNullReferenceException
            return new TwitterUser(ulong.Parse(user.Element("id").Value), user.Element("name").Value,
                                   user.Element("screen_name").Value, user.Element("location").Value,
                                   user.Element("description").Value, new Uri(user.Element("profile_image_url").Value),
                                   user.Element("url").Value, int.Parse(user.Element("followers_count").Value),
                                   int.Parse(user.Element("statuses_count").Value), true, listedCount, showInlineMedia,
                                   followRequestSent);
            // ReSharper restore PossibleNullReferenceException
        }
    }
}
